C++ 继任者?Carbon 语法速览!
The following article is from 编程往事 Author 果冻虾仁
7月19日Cpp North大会上,谷歌的C++专家Chandler Carruth发表了『C++: What Comes Next? (Announcing the Carbon Language experiment) 』的主题演讲,官宣了正在实验中的Carbon语言,其目标是成为C++的继任者。
目前Carbon语言在Github上已经开源,地址:https://github.com/carbon-language/carbon-lang
7月22日,Cpp North的Youtube频道公开了这一演讲的视频:https://www.youtube.com/watch?v=omrY53kbVoA&ab_channel=CppNorth
接下来让我们来速览一下Carbon的语法,首先声明本文的Carbon代码都是基于Chandler Carruth的Keynote以及当前Carbon的github仓库中的代码。
由于Carbon还在实验阶段,Chandler Carruth的Keynote中演示某些语法,当前的Carbon还不支持编译……,比如abstract class
、类外定义函数等等。当然已经实现的语法部分可能不稳定,后期也可能会再有重大调整也未可知。
由于当下并且没有支持Carbon语法的代码高亮插件,所以下面代码中的高亮并不准,敬请见谅。
Hello World
package ExplorerTest api;
第一行是定义了package(包),包名是ExplorerTest。后面的api,那个不是包名的一部分,但是又不能省略(当前可以是api或impl)。
fn Main() -> i32 {
var s: auto = "Hello world!";
Print(s);
return 0;
}
定义了main函数,Carbon中使用的Main()
。从这个Hello World的例子中,变量语法、函数定义语法可见一斑。
变量
var
即变量:
var x: i64 = 42;
x = 7;
笔者点评:个人感觉还不如C++的语法简洁……
int64_t x = 42;
冒号后面是变量的类型,i64是int64,也可以使用auto做类型推导:
var x: auto = 42;
let
基本等同于C++中常量,Rust也采用let
表示常量。
let x: i64 = 42;
同样也支持auto
let x: auto = 42;
控制流
if else
if (fruit.IsYellow()) {
Console.Print("Banana!");
} else if (fruit.IsOrange()) {
Console.Print("Orange!");
} else {
Console.Print("Vegetable!");
}
和C/C++一样。
while
var x: i32 = 0;
while (x < 3) {
Console.Print(x);
++x;
}
Console.Print("Done!");
也和C++一样
for
for (var step: Step in steps) {
if (step.IsManual()) {
Console.Print("Reached manual step!");
break;
}
if (step.NotReady()) {
continue;
}
step.Process();
}
语法是for-range循环的语法。同样也能发现Carbon支持break和continue。
match
Carbon中没有switch,但是有match:
fn Bar() -> (i32, (f32, f32));
fn Foo() -> f32 {
match (Bar()) {
case (42, (x: f32, y: f32)) => {
return x - y;
}
case (p: i32, (x: f32, _: f32)) if (p < 13) => {
return p * x;
}
case (p: i32, _: auto) if (p > 3) => {
return p * Pi;
}
default => {
return Pi;
}
}
}
不过C++中的switch能比较的只能是整型、枚举。
函数
fn Add(a: i64, b: i64) -> i64 {
return a + b;
}
Carbon中这个Add函数的写法和Rust中实现一个Add几乎一模一样。比如声明参数的时候类型在后,并且冒号分割的参数写法。还有fn
关键字。
当然在函数体内逻辑复杂的时候,会和Rust不同,因为Rust还有不带分号的表达式语法,表达式的值就是整个函数的值。Carbon没有那种怪异的东西。
另外Carbon也能进行返回值的类型推导:
fn Add(a: i64, b: i64) -> auto {
return a + b;
}
一个更复杂的函数的例子:
package Geometry api;
import Math; // 导入其他包
class Circle { // 定义一个Circle类
var r: f32;
}
fn ScaleAreaAndAppend(circle: Circle, log2_scale: i32,
results: Vector(f32)*) {
var area: f32 = Math.Pi * c.r * c.r;
let scale: i32 = 1 << log2_scale;
area *= scale;
results->append(area);
}
参数默认都是常量,除非声明成指针类型。
面向对象
类与对象
class Point {
var x: i32;
var y: i32;
}
fn Main() -> i32 {
var p1: Point = {.x = 1, .y = 2};
var p2: auto = p1;
p2.x = 3;
return p1.x - 1;
}
成员变量与成员函数
class NewsAriticle {
// 类似C++的static
fn Make(headline: String, body_html: String) -> NewsAritcle();
// 只读方法
fn AsHtml[me: Self]() -> String;
// 可修改方法
fn Publish[addr me: Self*]() { me->published = DateTime.Now(); }
private var headline: String;
private var body_html: String;
private var published: Optional(Datetime);
}
// 类外定义成员函数
fn NewsAriticle.AsHtml[me: Self]() -> String{ ... }
Carbon中类中成员访问控制权限默认都是public
,如果需要声明成私有则需要单独加private
关键字。这个行为和C/C++的struct相同,但是和主流语言的class都不同。
定义在类中的函数,如果有[me: Self]
表示是只读的成员函数,在函数中不能修改类对象的成员变量。me在函数体中是表示对当前对象的引用,类似C++的(*this)
。
如果有[addr me: Self*]
表示的是可对当前对象进行修改的函数。me在函数体中类似C++的this
指针。
[me: Self]
或[addr me: Self*]
的成员函数,也可以称作方法(method),如果类中的函数没有[me: Self]
或[addr me: Self*]
,则表示是一个和对象无关的函数,等价于C++中的static成员函数。这个设计很像python中的类中成员函数的设计。
继承与抽象
Carbon只支持单继承,这没的说。值得注意的是普通的class
关键字定义的类型默认都是final
的,即不能被继承生成子类(俗称『绝育』)。但abstrct class
和base class
关键字定义的类型可以被继承:
// 抽象类(abstract class)不能被实例化,因为其中可能包含抽象方法
abstract class UIWidget {
// 抽象方法(abstract fn)没有实现
abstract fn Draw[me: Self](s: Screen);
abstract fn Click[addr me: Self*](x: i32, y: i32);
}
// base class 允许扩展和实例化
base class Button extends UIWidget {
// 实现抽象方法
impl fn Draw[me: Self](s: Screen) { ... }
impl fn Click[addr me: Self*];
// 新增了一个虚函数(virtual fn)
virtual fn MoveTo[addr me: Self*](x: i32, y: i32);
}
// 类外实现方法
fn Button.Click[addr me: Self*](x: i32, y: i32) { ... }
fn Button.MoveTo[addr me: Self*](x: i32, y: i32) { ... }
class ImageButton extends Button {
...
}
abstrct class
就是抽象类,它不能被实例化,因为其中有抽象方法。抽象类与抽象方法的概念和Java类似。抽象方法等同于C++中的纯虚函数
。
base class
不仅是可以被继承(扩展)的类,还能实例化。因为它里面不会有抽象方法,所有继承而来的抽象方法都要被实现。base class
中也能用virtual
修饰成员函数,这个语法是从C++中的虚函数而来的。
泛型
泛型接口
定义泛型接口来做泛型代码的类型检查
interface Summary {
fn Summarize[me: Self]() -> String;
}
这个interface不是Java中的interface,而是有点像C++中的Concept,对泛型做类型检查用的。
泛型函数
fn PrintSummary[T:! Summary](x: T) {
Console.Print(x.Summarize());
}
定义了一个支持Summary泛型接口的泛型函数PrintSummary
实现泛型接口
class NewsArticle {
...
impl as Summary {
fn Summarize[me: Self]() -> String { ... }
}
}
所以可以使用泛型函数来调用实现了泛型接口的类对象
// n 是 NewsArticle类型
PrintSummary(n);
也可以直接调用
// n 是 NewsArticle类型
n.Summarize();
扩展其他包的API
import OtherPackage;
interface Summary {
fn Summarize[me: Self]() -> String;
}
// 泛型函数
fn PrintSummary[T:! Summary](x: T) {
Console.Print(x.Summarize());
}
// 扩展外部API的接口
external impl OtherPackege.Tweet as Summary {
fn Summarize[me: Self]() -> String { ... }
}
fn SummarizeTweet(t: Tweet) {
PrintSummary(t);
}
我们导入了一个外部的包OtherPackege,它之中有一个Tweet类型,然后我们可以通过external impl
来扩展它支持它本不存在的泛型接口。
指针
基本语法:
// 定义i32类型变量x,值为5
var x: i32 = 5;
// 把x的值改成10
x = 10;
// 定义i32*类型的指针p,指向变量x的地址
var p: i32* = &x;
// 通过指针修改x的值为7
*p = 7;
// 定义i32*类型的指针q,使用&*p,同样指向变量x的地址
var q: i32* = &*p;
// 通过指针q修改x的值为0
*q = 0;
// 定义一个i32类型的变量y,值为0
var y: i32 = *p;
另外Carbon的指针不支持空指针,如果想表示不存在,使用Optional。
与C++互操作
与C++互操作是Carbon宣传的重点,也是最大难点。现在Carbon语言还不完善,这里举一个Keynote中演示的例子。
有一个C++的头文件circle.h
struct Circle {
float r;
}
Carbon调用C++
然后编写一个Carbon代码文件:geometry.carbon
package Geometry api;
import Math;
import Cpp library "circle.h";
fn PrintArea(circles: Slice(Cpp.Circle)) {
var area: f32 = 0;
for (c: Cpp.Circle in circles) {
area += Math.Pi * c.r * c.r;
}
Print("Total area: {0}", area);
}
可以通过 import Cpp library "circle.h";
这种语法来引用C++头文件中声明的类型。
C++调用Carbon
写一个C++的源文件:
#include <vector>
#include "circle.h"
#include "geometry.carbon.h"
auto main(int argc, char* argv) -> int {
std::vector<Circle> circles = {{1.0}, {2.0}};
Geometry::PrintArea(circles);
return 0;
}
最后提醒
最后再次提醒,某些KeyNote中演示的Carbon语法仅仅是当前规划中的设计,但还没有被实现。当前已经实现,能够通过编译语法,参考Github仓库中explorer/testdata
目录中的代码。
- EOF -
加主页君微信,不仅C/C++技能+1
主页君日常还会在个人微信分享C/C++开发学习资源和技术文章精选,不定期分享一些有意思的活动、岗位内推以及如何用技术做业余项目
加个微信,打开一扇窗
关注『CPP开发者』
看精选C/C++技术文章
点赞和在看就是最大的支持❤️